package edu.ncsu.csc.ip.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Timer;

import edu.ncsu.csc.ip.client.protocol.ProtocolClientImpl;
import edu.ncsu.csc.ip.client.timer.TTLUpdateTask;
import edu.ncsu.csc.ip.common.Log;
import edu.ncsu.csc.ip.common.NetworkUtils;
import edu.ncsu.csc.ip.common.Protocol;
import edu.ncsu.csc.ip.server.ClientBean;

/**
 * This is the main class that starts the peer interactions. It registers with
 * the RS server, starts the RFC Server and also allows for a user to provide
 * input on what RFC to download. Though that process is also be automated.
 * 
 * 
 * 
 * @author Moin Ayazifar <mayazif@ncsu.edu>
 * @author Steven Elliott <sdelliot@ncsu.edu>
 * 
 */

public class PeerClient {

	public final static int TTL_UPDATE_INTERVAL = 300;
	private String rsServerAddress;
	private Integer rsServerPort;
	private Integer rfcServerPort;
	private String rfcBundleFolder;
	RFCServer rfcServer;
	RFCIndex rfcIndex;
	ActiveClientIndex activeClientIndex;
	ClientInfo clientInfo;
	Timer timer = null;

	public PeerClient(String serverAddress, Integer serverPort,
			Integer rfcServerPort, String rfcBundleFolder) {
		this.rsServerAddress = serverAddress;
		this.rsServerPort = serverPort;
		this.rfcServerPort = rfcServerPort;
		this.rfcBundleFolder = rfcBundleFolder;
		this.clientInfo = new ClientInfo();
		this.clientInfo.setRfcServerPort(this.rfcServerPort);
		this.clientInfo.setLocalAddress(NetworkUtils.getIPAdress());
		this.clientInfo.setRfcBundleFolder(this.rfcBundleFolder);
		this.rfcIndex = new RFCIndex(clientInfo);
		this.activeClientIndex = new ActiveClientIndex();
		this.rfcServer = new RFCServer(clientInfo, rfcIndex);
		rfcServer.start();
	}

	public long runCommand(String userCommand) throws IOException {
		Protocol protocol = new ProtocolClientImpl();
		Socket socket = null;
		PrintWriter out = null;
		BufferedReader in = null;
		long commandTimeMillis = 0;

		if (protocol.isRFCCommand(userCommand)) {
			if (protocol.isFileTransferCommand(userCommand)) {
				try {
					Integer rfcNumber = Integer
							.parseInt(userCommand.split(" ")[1].trim());
					if (rfcIndex.getRfcIndexMap().containsKey(rfcNumber)
							&& !rfcIndex.getLocalRfcIndexMap().containsKey(
									rfcNumber)) {
						// Start of DL time
						commandTimeMillis = System.currentTimeMillis();
						for (RFCBean rfcBean : rfcIndex.getRfcIndexMap().get(
								rfcNumber)) {
							try {
								socket = new Socket(rfcBean.getAddress(),
										rfcBean.getPort());
								protocol = new ProtocolClientImpl(socket,
										clientInfo, rfcIndex, activeClientIndex);
								out = new PrintWriter(socket.getOutputStream(),
										true);
								in = new BufferedReader(new InputStreamReader(
										socket.getInputStream()));

								String sendToserver = protocol
										.parseCommand(userCommand);
								if (sendToserver != null) {
									StringBuilder inputMessage = new StringBuilder();
									out.println(sendToserver);
									Log.printMessage("Message From "
											+ clientInfo + " To RFC Server ("
											+ rfcBean.getAddress() + ":"
											+ rfcBean.getPort() + "): ",
											sendToserver);

									String inputLine;
									String header = in.readLine();
									inputMessage.append(header);
									String dlResult = null;
									if (header != null) {
										while ((inputLine = in.readLine()) != null) {
											inputMessage.append("\n"
													+ inputLine);
										}
										Log.printMessage("Message received by "
												+ clientInfo
												+ " From RFC Server ("
												+ rfcBean.getAddress() + ":"
												+ rfcBean.getPort() + "): ",
												inputMessage.toString());
										dlResult = protocol
												.parseMessage(inputMessage
														.toString());
									}
									// File downloaded successfully
									if (dlResult != null)
										break;
									else {
										Log.println(clientInfo
												+ " Could not get RFC From: ("
												+ rfcBean.getAddress()
												+ ":"
												+ rfcBean.getPort()
												+ ". Will start trying next RFC Server.");
									}

								}

							} catch (UnknownHostException e) {
								Log.err("Couldn't connect to RFC Server: "
										+ rfcBean.getAddress() + ":"
										+ rfcBean.getPort());
							} catch (IOException e) {
								Log.err("Couldn't get I/O for the connection to: "
										+ rfcBean.getAddress()
										+ ":"
										+ rfcBean.getPort());
							} catch (Exception e) {
								Log.err("Couldn't connect to RFC Server: "
										+ rfcBean.getAddress() + ":"
										+ rfcBean.getPort());
							} finally {
								if (out != null)
									out.close();
								if (in != null)
									in.close();
								if (socket != null)
									socket.close();
							}
						}
						commandTimeMillis = System.currentTimeMillis()
								- commandTimeMillis;
						return commandTimeMillis;
					} else {
						if (rfcIndex.getLocalRfcIndexMap().containsKey(
								rfcNumber))
							Log.println(clientInfo
									+ "RFC is available locally.");
						else
							Log.println(clientInfo
									+ "RFC Index doesn't have the RFC entry: "
									+ rfcNumber
									+ " and/or is Empty. Please run "
									+ ProtocolClientImpl.RFCQUERY + " again!");
						return 0;
					}
				} catch (Exception e) {
					e.printStackTrace();
					Log.println(clientInfo
							+ "Command not sent/recognized! Please try again. message: "
							+ e.getMessage());
				}

			} else {
				if (activeClientIndex.getClients().size() > 0) {
					for (ClientBean clientBean : activeClientIndex.getClients()) {
						try {
							socket = new Socket(clientBean.getIpAddress(),
									clientBean.getRfcServerPort());
							protocol = new ProtocolClientImpl(socket,
									clientInfo, rfcIndex, activeClientIndex);
							out = new PrintWriter(socket.getOutputStream(),
									true);
							in = new BufferedReader(new InputStreamReader(
									socket.getInputStream()));

							String sendToserver = protocol
									.parseCommand(userCommand);
							if (sendToserver != null) {
								StringBuilder inputMessage = new StringBuilder();
								out.println(sendToserver);
								Log.printMessage(
										"Message From " + clientInfo
												+ " To RFC Server ("
												+ clientBean.getIpAddress()
												+ ":"
												+ clientBean.getRfcServerPort()
												+ "): ", sendToserver);

								String header = in.readLine();
								inputMessage.append(header);
								if (header != null) {
									inputMessage.append("\n" + in.readLine());
									Log.printMessage("Message received by "
											+ clientInfo + " From RFC Server ("
											+ clientBean.getIpAddress() + ":"
											+ clientBean.getRfcServerPort()
											+ "): ", inputMessage.toString());

									protocol.parseMessage(inputMessage
											.toString());
								}

							}

						} catch (UnknownHostException e) {
							Log.err("Couldn't connect to RFC Server: "
									+ clientBean.getIpAddress() + ":"
									+ clientBean.getRfcServerPort());
						} catch (IOException e) {
							Log.err("Couldn't get I/O for the connection to: "
									+ clientBean.getIpAddress() + ":"
									+ clientBean.getRfcServerPort());
						} finally {
							if (out != null)
								out.close();
							if (in != null)
								in.close();
							if (socket != null)
								socket.close();
						}
					}
				} else {
					Log.println(clientInfo
							+ "No Active Client is available. Consider Running PQUERY command first.");
				}
			}

		} else {
			try {
				socket = new Socket(rsServerAddress, rsServerPort);
				protocol = new ProtocolClientImpl(socket, clientInfo, rfcIndex,
						activeClientIndex);
				out = new PrintWriter(socket.getOutputStream(), true);
				in = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));

				String sendToserver = protocol.parseCommand(userCommand);
				if (sendToserver != null) {
					StringBuilder inputMessage = new StringBuilder();
					out.println(sendToserver);
					Log.printMessage("Message From " + clientInfo
							+ " To RS Server (" + rsServerAddress + ":"
							+ rsServerPort + "): ", sendToserver);

					String header = in.readLine();
					inputMessage.append(header);
					if (header != null) {
						inputMessage.append("\n" + in.readLine());

						Log.printMessage("Message received by " + clientInfo
								+ " From RS Server (" + rsServerAddress + ":"
								+ rsServerPort + "): ", inputMessage.toString());

						protocol.parseMessage(inputMessage.toString());

						// No need to send a message back to server
						// out.println(outputLine);
						
						//Activate Timer after REGISTER and 
						//deactivate Timer after LEAVE
						if (clientInfo.getCookie() != null && timer == null) {
							// Schedule a timer to update TTLs in client index
							timer = new Timer();
							timer.scheduleAtFixedRate(new TTLUpdateTask(rfcIndex, this),
									TTL_UPDATE_INTERVAL * 1000,TTL_UPDATE_INTERVAL * 1000);

						} else if (clientInfo.getCookie() == null && timer != null){
							timer.cancel();
							timer.purge();
							timer = null;
						}
					}

				} else {
					Log.println(clientInfo
							+ "Command not sent/recognized! Please Try another command.");
				}

			} catch (UnknownHostException e) {
				Log.err("Couldn't connect to RS: " + rsServerAddress + ":"
						+ rsServerPort);
				// System.exit(1);
			} catch (IOException e) {
				Log.err("Couldn't get I/O for the connection to RS: "
						+ rsServerAddress + ":" + rsServerPort);
				// System.exit(1);
			} finally {
				if (out != null)
					out.close();
				if (in != null)
					in.close();
				if (socket != null)
					socket.close();
			}

		}
		return -1;
	}

}
